use crate::types::{self, BcError};
use serde::de::DeserializeOwned;

#[derive(Debug, Default)]
pub struct RPCService {
    pub key: String,
    pub port: u16,
    pub client: reqwest::Client,
}

fn format_service_name(name: &str, port: u16) -> String {
    format!("{name}-service:{port}")
}

#[derive(Debug, thiserror::Error)]
pub enum RpcError {
    #[error("request errors `{0:?}`")]
    Request(Vec<BcError>),
    #[error("reqwest error `{0}`")]
    Reqwest(reqwest::Error),
}

impl RPCService {
    pub fn new() -> Self {
        Self::default()
    }

    pub async fn call<Req: Into<reqwest::Body>, Resp: Clone + DeserializeOwned>(
        &self,
        service: &str,
        method: reqwest::Method,
        function: &str,
        req: Req,
    ) -> Result<Resp, RpcError> {
        let service_name = format_service_name(service, self.port);
        let request = self
            .client
            .request(method, format!("http://{service_name}/rpc/{function}/"))
            .body(req)
            .header("X-Boticord-Rpc", &self.key)
            .header("Content-Type", "application/json")
            .build()
            .map_err(RpcError::Reqwest)?;
        let response = self
            .client
            .execute(request)
            .await
            .map_err(RpcError::Reqwest)?
            .json::<types::Response<Resp>>()
            .await
            .map_err(RpcError::Reqwest)?;

        Into::<Result<_, Vec<BcError>>>::into(response).map_err(RpcError::Request)
    }
}
